<!--
 * @Author       : 梁嘉琳
 * @Date         : 2022-05-27 11:11:29
 * @LastEditors  : 梁嘉琳
 * @LastEditTime : 2022-07-13 14:11:21
 * @FilePath     : \web3d\my\examples\haunted_house.html
-->
<!DOCTYPE html>
<html lang="zh">
	<head>
		<title>threejs学习</title>
		<meta charset="utf-8" />
		<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0" />
		<link rel="icon" href="../favicon.ico" />
		<link type="text/css" rel="stylesheet" href="../public/main.css" />
	</head>
	<body>
		<canvas id="fireflx"></canvas>

		<script type="importmap">
			{
				"imports": {
					"three": "../node_modules/three/build/three.module.js",
					"three/": "../node_modules/three/",
					"gui": "../node_modules/dat.gui/build/dat.gui.module.js"
				}
			}
		</script>
		<script type="module">
			import * as THREE from 'three';
			import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
			import * as dat from 'gui';
			let scene, canvas, gui, texture;

			init();
			function init() {
				canvas = document.querySelector('#fireflx');
				gui = new dat.GUI();
				scene = new THREE.Scene();
				loadTexture();
				initMesh();
			}
			function loadTexture() {
				const baseUrl = '../module_files/textures/';
				const door = ['color', 'alpha', 'ambientOcclusion', 'height', 'normal', 'metalness', 'roughness'];
				const bricks = [door[0], door[2], door[4], door[6]];
				const grass = [...bricks];
				texture = {
					door: {},
					bricks: {},
					grass: {},
				};
				const textureLoader = new THREE.TextureLoader();
				for (let i = 0; i < 3; i++) {
					if (i === 0) {
						door.forEach((v) => {
							texture.door[v] = textureLoader.load(baseUrl + 'door/' + v + '.jpg');
						});
					} else if (i === 1) {
						bricks.forEach((v) => {
							texture.bricks[v] = textureLoader.load(baseUrl + 'bricks/' + v + '.jpg');
						});
					} else {
						grass.forEach((v) => {
							texture.grass[v] = textureLoader.load(baseUrl + 'grass/' + v + '.jpg');
						});
					}
				}
				// console.log(texture);
			}

			function initMesh() {
				const house = new THREE.Group();
				scene.add(house);
				const houseHeight = 2.5,
					houseWidth = 4,
					roofHeight = 1;

				const walls = new THREE.Mesh(
					new THREE.BoxBufferGeometry(4, 2.5, 4),
					new THREE.MeshStandardMaterial({
						map: texture.bricks.color,
						aoMap: texture.bricks.ambientOcclusion,
						normalMap: texture.bricks.normal,
						roughnessMap: texture.bricks.roughness,
					})
				);
				walls.geometry.setAttribute(
					'uv2',
					new THREE.Float32BufferAttribute(walls.geometry.attributes.uv.array, 2)
				);
				walls.position.y = houseHeight / 2;
				house.add(walls);

				const roof = new THREE.Mesh(
					new THREE.ConeBufferGeometry(3.5, roofHeight, 4),
					new THREE.MeshStandardMaterial({ color: '#b35f45' })
				);
				roof.position.y = houseHeight + roofHeight / 2;
				roof.rotation.y = Math.PI / 4;
				house.add(roof);

				const door = new THREE.Mesh(
					new THREE.PlaneBufferGeometry(2.2, 2.2, 100, 100),
					new THREE.MeshStandardMaterial({
						map: texture.door.color,
						aoMap: texture.door.ambientOcclusion,
						normalMap: texture.door.normal,
						roughnessMap: texture.door.roughness,
						metalnessMap: texture.door.metalness,
						transparent: true,
						alphaMap: texture.door.alpha,
						displacementScale: 0.1,
						displacementMap: texture.door.height,
					})
				);
				door.geometry.setAttribute(
					'uv2',
					new THREE.Float32BufferAttribute(door.geometry.attributes.uv.array, 2)
				);
				door.position.set(0, 1, houseWidth / 2 + 0.0001);
				house.add(door);

				const bushGeometry = new THREE.SphereGeometry(1, 16, 16);
				const bushMaterial = new THREE.MeshStandardMaterial({ color: '#89c854' });

				const bush1 = new THREE.Mesh(bushGeometry, bushMaterial);
				bush1.scale.set(0.5, 0.5, 0.5);
				bush1.position.set(0.8, 0.2, 2.2);

				const bush2 = new THREE.Mesh(bushGeometry, bushMaterial);
				bush2.scale.set(0.25, 0.25, 0.25);
				bush2.position.set(1.4, 0.1, 2.1);

				const bush3 = new THREE.Mesh(bushGeometry, bushMaterial);
				bush3.scale.set(0.4, 0.4, 0.4);
				bush3.position.set(-0.8, 0.1, 2.2);

				const bush4 = new THREE.Mesh(bushGeometry, bushMaterial);
				bush4.scale.set(0.15, 0.15, 0.15);
				bush4.position.set(-1, 0.05, 2.6);

				house.add(bush1, bush2, bush3, bush4);

				const graves = new THREE.Group();
				scene.add(graves);

				const graveGeometry = new THREE.BoxBufferGeometry(0.6, 0.8, 0.2);
				const graveMaterial = new THREE.MeshStandardMaterial({ color: '#b2b6b1' });
				const graveCount = 50;

				for (let i = 0; i < graveCount; i++) {
					const angle = Math.random() * 2 * Math.PI;
					const r = 3 + Math.random() * 6;
					const x = Math.sin(angle) * r;
					const z = Math.cos(angle) * r;

					const grave = new THREE.Mesh(graveGeometry, graveMaterial);
					grave.position.set(x, 0.4, z);
					grave.rotation.y = (Math.random() - 0.5) * 0.4;
					grave.rotation.z = (Math.random() - 0.5) * 0.4;
					graves.add(grave);
				}

				// Floor
				const floor = new THREE.Mesh(
					new THREE.PlaneBufferGeometry(20, 20),
					new THREE.MeshStandardMaterial({
						map: texture.grass.color,
						aoMap: texture.grass.ambientOcclusion,
						normalMap: texture.grass.normal,
						roughnessMap: texture.grass.roughness,
					})
				);
				floor.geometry.setAttribute(
					'uv2',
					new THREE.Float32BufferAttribute(floor.geometry.attributes.uv.array, 2)
				);
				texture.grass.color.wrapS = THREE.RepeatWrapping;
				texture.grass.ambientOcclusion.wrapS = THREE.RepeatWrapping;
				texture.grass.normal.wrapS = THREE.RepeatWrapping;
				texture.grass.roughness.wrapS = THREE.RepeatWrapping;
				texture.grass.color.wrapT = THREE.RepeatWrapping;
				texture.grass.ambientOcclusion.wrapT = THREE.RepeatWrapping;
				texture.grass.normal.wrapT = THREE.RepeatWrapping;
				texture.grass.roughness.wrapT = THREE.RepeatWrapping;
				texture.grass.color.repeat.set(8, 8);
				texture.grass.ambientOcclusion.repeat.set(8, 8);
				texture.grass.normal.repeat.set(8, 8);
				texture.grass.roughness.repeat.set(8, 8);
				floor.rotation.x = -Math.PI * 0.5;
				floor.position.y = 0;
				scene.add(floor);

				const ambientLight = new THREE.AmbientLight('#b9d5ff', 0.12);
				gui.add(ambientLight, 'intensity').min(0).max(1).step(0.001);
				scene.add(ambientLight);

				// Directional light
				const moonLight = new THREE.DirectionalLight('#b9d5ff', 0.12);
				moonLight.position.set(4, 5, -2);
				gui.add(moonLight, 'intensity').min(0).max(1).step(0.001);
				gui.add(moonLight.position, 'x').min(-5).max(5).step(0.001);
				gui.add(moonLight.position, 'y').min(-5).max(5).step(0.001);
				gui.add(moonLight.position, 'z').min(-5).max(5).step(0.001);
				scene.add(moonLight);

				const doorLight = new THREE.PointLight('#ff7d46', 1, 7);
				doorLight.position.set(0, 2.2, 2.7);
				house.add(doorLight);

				const fog = new THREE.Fog('#262837', 1, 15);
				scene.fog = fog;

				/**
				 * Ghosts
				 */
				const ghost1 = new THREE.PointLight('#ff00ff', 2, 3);
				scene.add(ghost1);

				const ghost2 = new THREE.PointLight('#00ffff', 2, 3);
				scene.add(ghost2);

				const ghost3 = new THREE.PointLight('#ffff00', 2, 3);
				scene.add(ghost3);

				/**
				 * Sizes
				 */
				const sizes = {
					width: window.innerWidth,
					height: window.innerHeight,
				};

				window.addEventListener('resize', () => {
					// Update sizes
					sizes.width = window.innerWidth;
					sizes.height = window.innerHeight;

					// Update camera
					camera.aspect = sizes.width / sizes.height;
					camera.updateProjectionMatrix();

					// Update renderer
					renderer.setSize(sizes.width, sizes.height);
					renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
				});

				/**
				 * Camera
				 */
				// Base camera
				const camera = new THREE.PerspectiveCamera(75, sizes.width / sizes.height, 0.1, 100);
				camera.position.x = 4;
				camera.position.y = 2;
				camera.position.z = 5;
				scene.add(camera);

				// Controls
				const controls = new OrbitControls(camera, canvas);
				controls.enableDamping = true;

				/**
				 * Renderer
				 */
				const renderer = new THREE.WebGLRenderer({
					canvas: canvas,
				});
				renderer.setSize(sizes.width, sizes.height);
				renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
				renderer.setClearColor('#262837');
				renderer.shadowMap.enabled = true;

				floor.receiveShadow = true;
				moonLight.castShadow = true;
				doorLight.castShadow = true;
				ghost1.castShadow = true;
				ghost2.castShadow = true;
				ghost3.castShadow = true;
				walls.castShadow = true;
				bush1.castShadow = true;
				bush2.castShadow = true;
				bush3.castShadow = true;
				bush4.castShadow = true;
				for (let i = 0; i < graveCount; i++) {
					graves.children[i].castShadow = true;
				}
				moonLight.shadow.mapSize.width = 256;
				moonLight.shadow.mapSize.height = 256;
				moonLight.shadow.camera.far = 15;

				doorLight.shadow.mapSize.width = 256;
				doorLight.shadow.mapSize.height = 256;
				doorLight.shadow.camera.far = 7;

				ghost1.shadow.mapSize.width = 256;
				ghost1.shadow.mapSize.height = 256;
				ghost1.shadow.camera.far = 7;

				ghost2.shadow.mapSize.width = 256;
				ghost2.shadow.mapSize.height = 256;
				ghost2.shadow.camera.far = 7;

				ghost3.shadow.mapSize.width = 256;
				ghost3.shadow.mapSize.height = 256;
				ghost3.shadow.camera.far = 7;

				renderer.shadowMap.type = THREE.PCFSoftShadowMap;

				/**
				 * Animate
				 */
				const clock = new THREE.Clock();

				const tick = () => {
					const elapsedTime = clock.getElapsedTime();

					const ghost1Angle = elapsedTime * 0.5;
					ghost1.position.x = Math.cos(ghost1Angle) * 4;
					ghost1.position.z = Math.sin(ghost1Angle) * 4;
					ghost1.position.y = Math.sin(elapsedTime * 3);

					const ghost2Angle = -elapsedTime * 0.32;
					ghost2.position.x = Math.cos(ghost2Angle) * 5;
					ghost2.position.z = Math.sin(ghost2Angle) * 5;
					ghost2.position.y = Math.sin(elapsedTime * 4) + Math.sin(elapsedTime * 2.5);

					const ghost3Angle = -elapsedTime * 0.18;
					ghost3.position.x = Math.cos(ghost3Angle) * (7 + Math.sin(elapsedTime * 0.32));
					ghost3.position.z = Math.sin(ghost3Angle) * (7 + Math.sin(elapsedTime * 0.5));
					ghost3.position.y = Math.sin(elapsedTime * 4) + Math.sin(elapsedTime * 2.5);

					// Update controls
					controls.update();

					// Render
					renderer.render(scene, camera);

					// Call tick again on the next frame
					window.requestAnimationFrame(tick);
				};

				tick();
			}
		</script>
	</body>
</html>
